/* -+- file  ./kf2/GUI/Chat.cs -+- */
/*
 * kf2 jabber client
 * ~~~~~~~~~~~~~~~~~
 *
 * Copyright (C) 2006-2007 Przemysław Sitek
 * 				<przemyslaw.sitek at wp.pl> 
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using Habazie.kf2.Core;
using Habazie.kf2.Net;
using Habazie.kf2.Exposition;
using Habazie.kf2.Roster;

namespace Habazie.kf2.GUI
{
	
	public class Chat : SubWindow, IDisposable
	{
		private jabber.JID		m_JID;
		private Account			m_Account;
		private IChatView		m_History;
		private UltraTextView	m_Input;
		private Item			m_Item = null;
		private	Gtk.Label		m_Label;
		private Gtk.Image		m_StatusIcon;
		private Gtk.EventBox	m_EventBox = null;
		
		private static LinkedList<Chat> m_Chats;
		
		static Chat ()
		{
			m_Chats = new LinkedList<Chat> ();
		}
		
		public static ICollection<Chat> All {
			get { return m_Chats; }
		}
		
		public static Chat Get (Account acc, jabber.JID jid)
		{
			foreach (Chat c in m_Chats)
				if (c.GetAccount () == acc && c.GetJID () == jid)
					return c;
			return new Chat (acc, jid);
		}
		
		public static Chat GetIfExists (Account acc, jabber.JID jid)
		{
			foreach (Chat c in m_Chats)
				if (c.GetAccount () == acc && c.GetJID () == jid)
					return c;
			return null;
		}
		
		public static Chat Get (Item i)
		{
			foreach (Chat c in m_Chats)
				if (c.m_Item == i)
					return c;
			return new Chat (i);
		}
		
		
		public static Chat Get (Item i, string resource)
		{
			foreach (Chat c in m_Chats)
				if (c.m_Item == i && resource == c.GetJID ().Resource)
					return c;
			return new Chat (i);
		}
		
		
		/*
		 *
		 *   Constructors
		 *
		 *            [private]
		 *
		 */
		 
		
		private Chat (Item i) : this (i.Account, i.JabberID)
		{
			RosterItem = i;
		}
		
		private Chat (Account acc, jabber.JID jid) : base ()
		{
			/* Whether use tabbed interface */
			TabContainer container = null;
			if (Util.Config.GetBool ("use-tabbed-interface")) {
				if (Util.Config.GetBool ("single-window-interface")) {
					container = Util.Application.MainWindow.Tabs;
					container.Show ();
				} else {
					container = TabContainer.Last;
					if (container == null) {
						TabWindow win = new TabWindow ();
						container = win.Container;
						win.Show ();
					}
				}
			} else {
				/* Multi-window interface */
				TabWindow win = new TabWindow ();
				container = win.Container;
				win.Show ();
			}
			
			
			/* Tabbed interface ends here */
			
			m_JID = jid;
			m_Account = acc;
			
			
			InitGui ();
			Item i = m_Account.GetRosterRef().GetItem (jid);
			RosterItem = i;
			m_JID = jid;
			UpdateUserInfo ();

			m_Chats.AddFirst (this);
			DestroyEvent += WasDestroyed;
			StateEvent += OnStateEvent;

			container.AddChild (this);
		}
		
		
		///<summary>
		///	Initialize all widgets
		///<summary>
		private void InitGui ()
		{
			this.BorderWidth = 6;
		
			Gtk.VBox box = new Gtk.VBox (false, 6);
			
			Gtk.HBox hbox = new Gtk.HBox (false, 6);
			m_EventBox = new Gtk.EventBox ();
			m_StatusIcon = Gtk.Image.LoadFromResource ("offline.png");
			m_EventBox.Add (m_StatusIcon);
			m_EventBox.EnterNotifyEvent += OnMotionEnter;
			m_EventBox.LeaveNotifyEvent += OnMotionLeave;
			hbox.PackStart (m_EventBox, false, false, 0);
			m_Label = new Gtk.Label (m_JID.ToString ());
			m_Label.SetAlignment ((float)0.0, (float)0.5);
			m_Label.Ellipsize = Pango.EllipsizeMode.End;
			hbox.PackStart (m_Label, true, true, 0);
			box.PackStart (hbox, false, false, 0);
			
			try {
	//			m_History = (IChatView) new GeckoChatView (jid.ToString ());
				m_History = Reveal.CreateChatView ();
				m_History.GetWidget().Show ();
				box.PackStart (m_History.GetWidget (), true, true, 0);
			} catch (Exception e) {
				Console.Error.WriteLine ("Ex: {0}", e.Message);
			}
			
			Gtk.ScrolledWindow sv = new Gtk.ScrolledWindow ();
			sv.VscrollbarPolicy = Gtk.PolicyType.Automatic;
			sv.HscrollbarPolicy = Gtk.PolicyType.Automatic;
			sv.ShadowType = Gtk.ShadowType.In;
			box.PackStart (sv, false, false, 0);
			
			UltraTextBuffer buffer = new UltraTextBuffer ();
			buffer.ComposingStart += OnComposingStart;
			buffer.ComposingStop += OnComposingStop;
			m_Input = new UltraTextView (buffer);
			m_Input.GrabDefault ();
			m_Input.WrapMode = Gtk.WrapMode.WordChar;
			m_Input.Activated += OnActivated;
//			m_Input.Buffer.Changed += OnChanged;
			
			sv.Add (m_Input);
			//Focus = m_Input;
			
			Add (box);
			box.ShowAll ();
			m_Input.ConfigureHeight ();
			this.SetDefaultSize (300, 240);
			Title = m_JID.ToString ();
		}
		
		
		public Account GetAccount ()
		{
			return m_Account;
		}
		
		public jabber.JID GetJID ()
		{
			return m_JID;
		}
		
		
		public jabber.JID JabberID {
			get { return m_JID;	}
		}
		
		
		public Item RosterItem {
			get { return m_Item; }
			set {
				if (m_Item != null)
					m_Item.StatusChanged -= OnStatusChanged;
				m_Item = value;
				if (m_Item != null) {
					if (m_Item.VCard == null && m_Item.Avatar.Pixbuf == null)
						m_Item.UpdateVCard ();
					m_JID = m_Item.JabberID;
					m_Item.StatusChanged += OnStatusChanged;
				}
				UpdateUserInfo ();
			}
		}
		
		
		///<summary>Cause this chat window to send message</summary>
		public void Send ()
		{
			if (! m_Account.IsAuthenticated) {
				// TODO: Error handling
				return;
			}
			
			string body = m_Input.Buffer.Text;
			if (body != "") {
				Message msg = new Message (m_Account.Document);
				msg.To = m_JID;
				msg.Type = jabber.protocol.client.MessageType.chat;
				msg.Body = body;
				m_state = ChatState.Active;
				if (Util.Config.GetBool ("enable-chat-notifications")) {
					System.Xml.XmlElement c = this.m_Account.Document.CreateElement ("active", "http://jabber.org/protocol/chatstates");
					msg.AppendChild (c);
				}
				try {
					m_Account.Write (msg);
					string me = Util.Config.GetString ("nickname");
					if (me == null || me == "")
						me = "me";
					m_History.DisplayMessage (me, System.DateTime.Now, false, body, m_Account.SelfItem.Avatar);

					Util.PlaySound ("chat-out");
				} catch (Exception e) {
					// TODO: add error handling code
					Util.ReportException (e);
				} finally {
					m_History.ScrollToEnd ();
					m_Input.Buffer.Clear ();
					m_Input.ConfigureHeight ();
				}
			}
		}
		
		
		public override bool IsDetachable ()
		{
			return m_History.CanReparent;
		}
		
		
		///<summary>
		///	Display a message
		///</summary>
		public void DisplayMsg (Message msg)
		{
			if (msg.Body != null) {
				if (m_Item != null)
					m_History.DisplayMessage (m_Item.Name, msg.DateSent, true, msg.Body, m_Item.Avatar);
				else
					m_History.DisplayMessage (msg.From, msg.DateSent, true, msg.Body, null);
				
				m_History.ScrollToEnd ();
				if (! IsActive)
					NumEvents++;
				else
					NumEvents = 0;
			}
			
			foreach (System.Xml.XmlNode n in msg) {
				if (n.NamespaceURI == "http://jabber.org/protocol/chatstates") {
					/* We got chat state notification */
					switch (n.LocalName) {
						case "active":		m_state_remote = ChatState.Active ; break;
						case "composing":	m_state_remote = ChatState.Composing ; break;
						case "paused":		m_state_remote = ChatState.Paused ; break;
						case "inactive":	m_state_remote = ChatState.Inactive ; break;
						case "gone":		m_state_remote = ChatState.Gone ; break;
					}
					m_remote_wants_notifications = true;
					UpdateUserInfo ();
				}
			}
		}
		
		
		///<summary>Input text area was activated</summary>
		protected virtual void OnActivated (object o, System.EventArgs args)
		{
			Send ();
		}
		
		
		/// <summary>
		/// 	Contact has changed status information 	
		/// </summary>
		protected virtual void OnStatusChanged (object sender, StatusChangedEventArgs args)
		{
			Item i = (Item) sender;
			Resource res = args.Resource;
			if (res.Name == m_JID.Resource) {
				Util.Debug ("chat", "right resource");
				string text = String.Format ("{0} is now {1}{2}",
							i.Name, Util.StatusToString (res.Presence, res.Show),
							(res.Status != null)?(": "+res.Status):"");
				m_History.DisplayEvent (m_JID.ToString (), DateTime.Now, text);
			}
			UpdateUserInfo ();
		}
		
		
		/// <summary>
		/// 	Update labels, status informations etc
		/// </summary>
		protected virtual void UpdateUserInfo ()
		{
			string title;
			if (m_Item != null) {
				m_Label.Markup = String.Format ("{0} <i><small>&lt;{1}&gt;</small></i>",
									m_Item.Name, m_JID.ToString ());
				title = m_Item.Name;
			} else {
				m_Label.Text = m_JID.ToString ();
				title = m_JID.ToString ();
			}
			
			/* Update status icon */
			if (m_Item != null) {
				Resource r = m_Item.GetResource (this.m_JID.Resource);
				if (r != null) {
					this.m_StatusIcon.Pixbuf = Pixbufs.Get (r.Presence, r.Show);
					this.Icon = Pixbufs.Get (r.Presence, r.Show);
				}
			}
	
			ShortTitle = title;		
	
			/* Append state info to titlebar text */
			if (m_state_remote == ChatState.Composing)
				title = title + " (Composing...)";
			else if (m_state_remote == ChatState.Inactive)
				title = title + " (Inactive)";
			else if (m_state_remote == ChatState.Gone)
				title = title + " (Gone)";
			this.Title = title;
		}
		
		public override void Dispose ()
		{
			if (m_remote_wants_notifications) {
				m_state = ChatState.Gone;
				SendEvent ("gone");
			}
			Hide ();
			m_Chats.Remove (this);
			Destroy ();
		}
		
		protected virtual void WasDestroyed (object o, Gtk.DestroyEventArgs args)
		{
			Dispose ();
		}
		
		private NotifyWindow m_StatusTooltip = null;
		
		
		/// <summary>
		/// 	Show user info popup window 	
		/// </summary>
		protected virtual void OnMotionEnter (object o, EventArgs args)
		{
			if (m_Item != null) {
				Resource r = m_Item.GetResource (m_JID.Resource); // FIXME
				if (r == null)
					return;
					
				m_StatusTooltip = new NotifyWindow ();
				m_StatusTooltip.Pixbuf = m_Item.Avatar.Pixbuf;
				m_StatusTooltip.TextMarkup = "<b>"+r.Name+"</b>";
				if (r.Status != null && r.Status.Length > 0)
					m_StatusTooltip.Desc = r.Status;
	
				int wx, wy, width, height;
				Gtk.Widget w = m_EventBox;
				w.GdkWindow.GetOrigin (out wx, out wy);
				w.GdkWindow.GetSize (out width, out height);
				m_StatusTooltip.Move (wx, wy + height + 6);
				
				m_StatusTooltip.Show ();
			}
		}
		
		
		protected virtual void OnMotionLeave (object o, EventArgs args)
		{
			if (m_StatusTooltip != null) {
				m_StatusTooltip.Hide ();
				m_StatusTooltip.Destroy ();
			}
			m_StatusTooltip = null;
		}
		
		protected virtual void OnStateEvent (object o, EventArgs args)
		{
			Util.Debug ("chat", "[{0}] active={1}", Title, IsActive);
			if (IsActive)
				NumEvents = 0;

			/* Chat state notifications */
			if (! m_remote_wants_notifications)
				return;
			if (IsActive) {
				m_state = ChatState.Active;
				SendEvent ("active");
			} else {
				m_state = ChatState.Inactive;
				SendEvent ("inactive");
			}
		}

		
#region Chat State Notifications
		enum ChatState {
			Active,
			Composing,
			Paused,
			Inactive,
			Gone
		}
		
		private ChatState m_state	= ChatState.Active;
		private bool m_remote_wants_notifications = false;
		private ChatState m_state_remote = ChatState.Active;

		protected virtual void OnComposingStart (object o, EventArgs args)
		{
			Util.Debug ("Chat", "ComposingStart");
			if (! Util.Config.GetBool ("enable-chat-notifications"))
				return;
			if (! m_remote_wants_notifications)
				return;

			/* We are active, and user began typing */
			m_state = ChatState.Composing;
			SendEvent ("composing");
		}
		
		
		/// <summary>
		/// 	Default handler for ComposingStop event.
		/// </summary>
		protected virtual void OnComposingStop (object o, EventArgs args)
		{
			Util.Debug ("Chat", "ComposingStop");
			if (! Util.Config.GetBool ("enable-chat-notifications"))
				return;
			if (! m_remote_wants_notifications)
				return;

			if (m_state == ChatState.Composing) {
				m_state = ChatState.Paused;
				SendEvent ("paused");
			}
		}

		
		/// <summary>
		/// 	Send a chat state notification
		/// </summary>
		private void SendEvent (string type)
		{
			jabber.protocol.client.Message msg = new jabber.protocol.client.Message (m_Account.Document);
			msg.To = m_JID;
			msg.Type = jabber.protocol.client.MessageType.chat;
			System.Xml.XmlElement c = this.m_Account.Document.CreateElement (type, "http://jabber.org/protocol/chatstates");
			msg.AppendChild (c);
			this.m_Account.Write (msg);
		}
		
#endregion
	}
}
